/**
 * ble to uart
 */
#include <stdio.h>
#include <string.h>
#include <zephyr/device.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/ring_buffer.h>
#include <zephyr/usb/usb_device.h>
#include <zephyr/usb/usbd.h>
#include <zephyr/logging/log.h>
#include "serial.h"

LOG_MODULE_REGISTER(uart_driver, LOG_LEVEL_INF);

const struct device *const uart_dev = DEVICE_DT_GET_ONE(zephyr_cdc_acm_uart);

#define RING_BUF_SIZE 512

struct ring_buf tx_rb;
struct ring_buf rx_rb;
uint8_t rx_ring_buffer[RING_BUF_SIZE];
uint8_t tx_ring_buffer[RING_BUF_SIZE];
static bool rx_throttled;

struct serial_inst m_serial_inst;
static serial_rx_proccess_t m_rx_proccess;

struct ble_data_item
{
    void *fifo_reserved;
    uint8_t data[512];
    uint16_t len;
};

struct ble_data_item tx_data;

K_SEM_DEFINE(uart_rx_sem, 0, 1);
K_FIFO_DEFINE(uart_tx_queue);

/**
 * ble controller to host
 */
static int uart_c2h_send(uint8_t *data, uint16_t len)
{
    int btw = ring_buf_put(&tx_rb, data, len);
    if (btw < len)
    {
        LOG_ERR("Drop %u bytes", btw - len);
    }
    uart_irq_tx_enable(uart_dev);
    return btw;
}

static inline void print_baudrate(const struct device *dev)
{
    uint32_t baudrate;
    int ret;

    ret = uart_line_ctrl_get(dev, UART_LINE_CTRL_BAUD_RATE, &baudrate);
    if (ret)
    {
        LOG_WRN("Failed to get baudrate, ret code %d", ret);
    }
    else
    {
        LOG_INF("Baudrate %u", baudrate);
    }
}

static void interrupt_handler(const struct device *dev, void *user_data)
{
    ARG_UNUSED(user_data);

    while (uart_irq_update(dev) && uart_irq_is_pending(dev))
    {
        if (!rx_throttled && uart_irq_rx_ready(dev))
        {
            int recv_len, rb_len;
            uint8_t buffer[64];
            size_t len = MIN(ring_buf_space_get(&rx_rb),
                             sizeof(buffer));

            if (len == 0)
            {
                /* Throttle because ring buffer is full */
                uart_irq_rx_disable(dev);
                rx_throttled = true;
                continue;
            }

            recv_len = uart_fifo_read(dev, buffer, len);
            if (recv_len < 0)
            {
                LOG_ERR("Failed to read UART FIFO");
                recv_len = 0;
            };

            rb_len = ring_buf_put(&rx_rb, buffer, recv_len);
            if (rb_len < recv_len)
            {
                LOG_ERR("Drop %u bytes", recv_len - rb_len);
            }

            k_sem_give(&uart_rx_sem);
        }

        if (uart_irq_tx_ready(dev))
        {
            uint8_t buffer[64];
            int rb_len, send_len;

            rb_len = ring_buf_get(&tx_rb, buffer, sizeof(buffer));
            if (!rb_len)
            {
                LOG_DBG("Ring buffer empty, disable TX IRQ");
                uart_irq_tx_disable(dev);
                continue;
            }

            if (rx_throttled)
            {
                uart_irq_rx_enable(dev);
                rx_throttled = false;
            }

            send_len = uart_fifo_fill(dev, buffer, rb_len);
            if (send_len < rb_len)
            {
                LOG_ERR("Drop %d bytes", rb_len - send_len);
            }

            LOG_DBG("ringbuf -> tty fifo %d bytes", send_len);
        }
    }
}

static int data_ready = 0;
static void uart_tx_thread(void *d0, void *d1, void *d2)
{
    struct ble_data_item *tx_data;

    for (;;)
    {
        tx_data = k_fifo_get(&uart_tx_queue, K_FOREVER);
        if (tx_data)
        {
            // ble to uart
            if (!uart_c2h_send(tx_data->data, tx_data->len))
            {
                LOG_ERR("Failed to uart c2h send.");
            }
        }
    }
}

static void uart_rx_thread(void *d0, void *d1, void *d2)
{
    int rb_len;
    uint8_t buffer[64];

    for (;;)
    {
        if (k_sem_take(&uart_rx_sem, K_FOREVER) == 0)
        {
            rb_len = ring_buf_get(&rx_rb, buffer, sizeof(buffer));
            if (rb_len)
            {
                // rx data handle
                // LOG_HEXDUMP_INF(buffer, rb_len, "rx data");
                if (m_rx_proccess)
                {
                    m_rx_proccess(buffer, rb_len);
                }
            }
            else
            {
                LOG_DBG("Ring buffer empty, disable TX IRQ");
            }
        }
    }
}

K_THREAD_DEFINE(uart_tx_thread_tid, 512, uart_tx_thread, NULL, NULL, NULL, 6, 0, 0);
K_THREAD_DEFINE(uart_rx_thread_tid, 512, uart_rx_thread, NULL, NULL, NULL, 7, 0, 0);

static int serial_tx(const uint8_t *const data, uint16_t len)
{
    if (!data || (len == 0))
        return 0;

    memcpy(tx_data.data, data, len);
    tx_data.len = len;
    k_fifo_put(&uart_tx_queue, &tx_data);

    return len;
}

struct serial_inst *serial_init(serial_rx_proccess_t rx_proccess)
{
    int ret;

    if (!device_is_ready(uart_dev))
    {
        LOG_ERR("CDC ACM device not ready");
        return NULL;
    }

    ret = usb_enable(NULL);
    if (ret != 0)
    {
        LOG_ERR("Failed to enable USB");
        return NULL;
    }

    ring_buf_init(&rx_rb, sizeof(rx_ring_buffer), rx_ring_buffer);
    ring_buf_init(&tx_rb, sizeof(tx_ring_buffer), tx_ring_buffer);

    LOG_INF("Wait for DTR");

    while (true)
    {
        uint32_t dtr = 0U;

        uart_line_ctrl_get(uart_dev, UART_LINE_CTRL_DTR, &dtr);
        if (dtr)
        {
            break;
        }
        else
        {
            /* Give CPU resources to low priority threads. */
            k_sleep(K_MSEC(100));
        }
    }

    LOG_INF("DTR set");

    /* They are optional, we use them to test the interrupt endpoint */
    ret = uart_line_ctrl_set(uart_dev, UART_LINE_CTRL_DCD, 1);
    if (ret)
    {
        LOG_WRN("Failed to set DCD, ret code %d", ret);
    }

    ret = uart_line_ctrl_set(uart_dev, UART_LINE_CTRL_DSR, 1);
    if (ret)
    {
        LOG_WRN("Failed to set DSR, ret code %d", ret);
    }

    /* Wait 100ms for the host to do all settings */
    k_msleep(100);

    print_baudrate(uart_dev);
    uart_irq_callback_set(uart_dev, interrupt_handler);

    /* Enable rx interrupts */
    uart_irq_rx_enable(uart_dev);

    m_serial_inst.tx = serial_tx;
    m_rx_proccess = rx_proccess;

    LOG_INF("Serial driver init OK.");

    return &m_serial_inst;
}
